﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using Isuka.Iteration;

namespace Isuka.Spiritual.Auxiliaries
{
    public static class RuleExpression
    {

        /// <summary>
        /// ルールに一致したときにアクションを起こす
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="inner"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc Act<T, A>(this Rules<T, A>.RuleFunc inner, Action<A> action)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                var r = inner(i, c, s);
                if (r.Match)
                {
                    action(c.Argument);
                }
                return r;
            };
        }

        /// <summary>
        /// ルールに一致したときに、アクションを起こす
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="inner"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc Act<T, A>(this Rules<T, A>.RuleFunc inner, Action<A, T> action)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                i = s(i, c);
                var w = i.Copy();
                var r = inner(i, c, s);
                if (r.Match)
                {
                    action(c.Argument, w.Current);
                }
                return r;
            };
        }

        /// <summary>
        /// ルールに一致したときに、アクションを起こす
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="inner"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc Act<T, A, V>(this Rules<T, A>.RuleFunc inner, Action<A, V> action, Func<IEnumerable<T>, V> conv)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Requires<ArgumentNullException>(action != null, "action");
            Contract.Requires<ArgumentNullException>(conv != null, "conv");
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                i = s(i, c);
                var w = i.Copy();
                var r = inner(i, c, s);
                if (r.Match)
                {
                    try
                    {
                        action(c.Argument, conv(Iterable.ToEnumerable(w, r.Iterator)));
                    }
                    catch (Exception)
                    {
                        return Rules<T, A>._False(i);
                    }
                }
                return r;
            };
        }

        /// <summary>
        /// 2つのルール双方に一致することを確認する。
        /// 最後の結果が採用されるため、副作用に注意
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc And<T, A>(this Rules<T, A>.RuleFunc left, Rules<T, A>.RuleFunc right)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                var r = left(i.Copy(), c, s);
                if (r.Match)
                {
                    r = right(i, c, s);
                }
                return r;
            };
        }

        /// <summary>
        /// ルールを判断する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc Or<T, A>(this Rules<T, A>.RuleFunc left, Rules<T, A>.RuleFunc right)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                var r = left(i.Copy(), c, s);
                return r.Match ? r : right(i, c, s);
            };
        }

        /// <summary>
        /// ルールを連結する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc Pipe<T, A>(this Rules<T, A>.RuleFunc left, Rules<T, A>.RuleFunc right)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                var r = left(i, c, s);
                return r.Match ? right(r.Iterator, c, s) : r;
            };
        }

        /// <summary>
        /// 1回以上一致
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="inner"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc OneOrMore<T, A>(this Rules<T, A>.RuleFunc inner)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                var r = inner(i, c, s);
                for (var t = r; t.Match; t = inner(t.Iterator, c, s))
                {
                    // inner が一致する限り、その結果で r を更新する
                    r = t;
                }
                return r;
            };
        }

        /// <summary>
        /// ゼロ回以上一致
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="inner"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc ZeroOrMore<T, A>(this Rules<T, A>.RuleFunc inner)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                var r = Rules<T, A>._True(i);
                for (var t = r; t.Match; t = inner(t.Iterator, c, s))
                {
                    // inner が一致する限り、その結果で r を更新する
                    r = t;
                }
                return r;
            };
        }

        /// <summary>
        /// ゼロまたは1回
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="A"></typeparam>
        /// <param name="inner"></param>
        /// <returns></returns>
        public static Rules<T, A>.RuleFunc ZeroOrOne<T, A>(this Rules<T, A>.RuleFunc inner)
            where T : IEquatable<T>
            where A : class
        {
            Contract.Ensures(Contract.Result<Rules<T, A>.RuleFunc>() != null);

            return (i, c, s) =>
            {
                var r = inner(i.Copy(), c, s);
                return r.Match ? r : Rules<T, A>._True(i);
            };
        }
    }
}
